-- | The "Strategy" module implements the dynamic strategy of AllYourLambdaAreBelongToUs. 
-- Please find further explanation of the strategy in the README file

module Strategy where
import Environment.Representation
import Environment.Game

import Debug.Trace

-- | Representation of a Strategy. A Strategy is a Stack of cards that should be played.
data Strategy = NormalStrategy [Action] -- ^ standard Strategy, can be interrupted by InteruptingStrategys 
              | InteruptingStrategy ([Action], Strategy) -- ^ Interrupts other Strategies, if Posible continue with the previous Strategy, can't be interrupted



-- Fields used by this Bot
-- Field 0 = We count in this field
-- Field 1 = healN
-- Field 2 = attackN
-- Field 3 = mostly used to build some stuff (if dead it is automatically switched to another field)
attackN, healN, field0minLife :: Int
attackN = 11112 --  Value that should be used in every Attack Function
healN = 9999 --  Value that should be used in every Heal Function
field0minLife = 50000 --  Minimum Health of field 0 before attacking another field


-- | gets the Next Card that should be played
getNextAction::Strategy->Game->(Action, Strategy)
getNextAction s g = if testKillSlot0 g 
                     then -- if slot0 can be killed then do it
                         ((2,Get,254), s)
                     else getNextAction' s g -- else get the real action (continue with our current strategy)

-- | get next Card of this Strategy or choose a new Strategy
getNextAction'::Strategy -> Game -> (Action, Strategy)
-- there was a interuptingStrategy finished, continue with the first strategy
getNextAction' (InteruptingStrategy ([], s)) g = getNextAction s g
-- normal Strategy is finished, start a new Strategy
getNextAction' (NormalStrategy []) g = if (notAlive > -1)
                        then getNextAction (newRevive (NormalStrategy []) notAlive g) g
                        else if ((getVitality (prop g) 0) > field0minLife)
                                then getNextAction (newAttack g) g
                                else getNextAction (newHeal g) g
                        where notAlive = getNotAlive g
-- continue with current strategy
getNextAction' (InteruptingStrategy ((x:xs), s)) g = (x, (InteruptingStrategy (xs, s)))
-- test if the current strategy should be interrupted, else continue
getNextAction' (NormalStrategy (x:xs)) g = if (notAlive == -1)
                                then (x, (NormalStrategy xs))
                                else getNextAction (newRevive (NormalStrategy (x:xs)) notAlive g) g
                        where notAlive = getNotAlive g


-- | tests if the first 4 slots are alive, and another slot which is used to build our Card-Stacks on
getNotAlive::Game -> Int
getNotAlive g = allAliveHelper 0 (prop g)
                where allAliveHelper::Int->Environment->Int
                      allAliveHelper 4 e = if (isAlive e 4)
                                                        then -1
                                                        else isAnyOtherSlotAlive g
                      allAliveHelper i e = if (isAlive e i)
                                                then (allAliveHelper (1+i) e)
                                                else i
                                                
-- | tests if any other slots expect the first 3 is alive
isAnyOtherSlotAlive::Game->Int
isAnyOtherSlotAlive g = isAnyOtherSlotAlive' 5 (prop g)
                        where isAnyOtherSlotAlive'::Int->Environment->Int
                              isAnyOtherSlotAlive' 254 e = if isAlive e 255 --Slot 254 is used for killing slot0 fast, so do not use this one
                                                              then -1
                                                              else 5
                              isAnyOtherSlotAlive' slotIndex e = if isAlive e slotIndex
                                                                   then -1
                                                                   else isAnyOtherSlotAlive' (1 + slotIndex) e     
                              

-- | A test if slot0 can be killed instantly (tests if enemy slot 0 is alive and has less then 5 live) 
testKillSlot0::Game->Bool
testKillSlot0 g = (isAlive (prop g) 254) && ((getVitality (op g) 0) < 5) && (isAlive (op g) 0)

-- | A set of cards to reach a certain number in a field (if there is a number in the field it will try to reuse this number)
countToNumber::Int->Int->Environment->[Action]
-- Counts to number i on field free. If possible (and useful) use the value allredy in field free.
countToNumber i free e = if slotNumber < 0
                            then countToNumber' i free -- 0 contains I
                            else if slotNumber > i -- 0 contains number
                                then startNew -- 0 contains number bigger then i
                                else if (length (continue slotNumber i free 0)) < (length startNew)
                                        then (continue slotNumber i free 0) -- continue is faster
                                        else startNew -- start new is faster

                         where slotNumber = isNumber slot
                               slot = getSlotI free e
                               continue::Int->Int->Int->Int->[Action]
                               continue start goal free counter = 
                                                           if (start == goal) || (counter > startNewLength)
                                                                then []
                                                                else if start <= (div goal 2)
                                                                        then if (mod goal 2) == 1
                                                                               then (continue start (goal -1) free (counter + 1)) ++ [(1,Succ,free)]
                                                                               else (continue start (div goal 2) free (counter + 1)) ++[(1,Dbl,free)]
                                                                        else [(1,Succ,free)]++ (continue (1+start) goal free (counter + 1))                               
                               startNew = [(1,Put,free)]++ (countToNumber' i free)
                               startNewLength = (length startNew)
             
             
-- | A set of cards to count to a number, starting with a Field that is I
countToNumber':: Int -> Int -> [Action]
countToNumber' 0 free = [(2,Zero,free)]
countToNumber' i free = if (mod i 2) == 1
                          then  (countToNumber' (i-1) free) ++ [(1,Succ,free)]
                          else  (countToNumber' (div i 2) free) ++ [(1,Dbl,free)]

-- | test if Slot is number, if not returns -1
isNumber::Slot->Int
isNumber (_, VInt i) = i
isNumber _ = -1


-- | A set of cards that get the value of slot i
getValueOfSlotI::Int->Int->[Action]
getValueOfSlotI buildOn i = if (i == buildOn) then [] else
                            [(1,K,buildOn),
                             (1,S,buildOn),
                             (2,Get,buildOn)] ++
                             buildRest buildOn i
            where buildRest buildOn 0 = [(2,Zero,buildOn)]
                  buildRest buildOn i =  [(1,K,buildOn),
                                          (1,S,buildOn),
                                          (2,Succ,buildOn)] ++
                                         buildRest buildOn (i-1)

-- | returns a Strategy to attack a living Field
newAttack::Game->Strategy
newAttack g = if (getLife ( fst (getSlotI (255 - fieldAttack) (op g)))) == 1
                then (NormalStrategy -- Kill with increment
                          ((countToNumber fieldAttack 0 (prop g))
                        ++ (getValueOfSlotI fieldBuildOn 0)
                        ++ [(1,Dec,fieldBuildOn)]))
                else -- Kill with an attack that does 10000 damage
                  (NormalStrategy 
                    ((initAttack (prop g))
                    ++ (countToNumber fieldAttack 0 (prop g))
                    ++ [(2,Attack,fieldBuildOn),
                        (2,Zero,  fieldBuildOn)]
                    ++ (getValueOfSlotI fieldBuildOn 0)
                    ++ (getValueOfSlotI fieldBuildOn 2))) -- ATTAAAACK
              where fieldAttack = chooseAttackField g -- the field that should be attacked
                    fieldBuildOn = getFieldBuildOn g -- the field on which the attacked is build on

-- | Gets a Free living slot
getFieldBuildOn::Game->Int
getFieldBuildOn g = if getFieldBuildOn' > 50 -- field within the first 50 slots that has the most life and contains I
                        then getFirstI (prop g)
                        else getFieldBuildOn'
                    where getFieldBuildOn' = getFreeFieldWithLife' (-1) 4 0 (prop g)

-- | Finds the index of the first Field containing I
getFirstI::Environment->Int
getFirstI e = getFirstI' e 4

getFirstI'::Environment->Int->Int
getFirstI' e 256 = -1
getFirstI' e i = if slotIsI (getSlotI i e)
                   then i
                   else getFirstI' e (i+1)

-- | Test if a slot is I
slotIsI::Slot->Bool
slotIsI (_, VCard I) = True
slotIsI _ = False

-- | Find the field which should be attacked next
chooseAttackField::Game->Int
chooseAttackField g = if number >= 0 -- if there is a number in field 0 of our environment, try to use this number
                        then if (isAlive (op g) (255-number)) -- if field is alive attack it, else take the next field
                               then number
                               else getNextLivingField (op g) number
                        else getNextLivingField (op g) 0 -- if there is no number in our field 0, just attack the next living enemy field
                        where slot0::Slot
                              slot0 = getSlotI 0 (prop g)
                              number::Int -- the number saved in slot0 (if there is no number in slot 0 then -1)
                              number = if (isAlive (prop g) 0)
                                        then getNumber slot0
                                        else -1
                              getNextLivingField::Environment->Int->Int --get the first living field
                              getNextLivingField e i = if (i <= 255) && (i >= 0)
                                                        then if (isAlive e (255 - i))
                                                              then i
                                                              else getNextLivingField e (i+1)
                                                        else getNextLivingField e 0
                              getNumber::Slot->Int -- returns a value between 0 and 255 if the slot contains a number, -1 if it doesn't
                              getNumber (_,VInt i) = if i < 256 then i else -1
                              getNumber (_,_) = -1


-- | Heals field 0. 
newHeal::Game->Strategy 
newHeal g = if (fieldIndex < 1) then 
                multyplyLife g -- there is no field that can be used for heal, use our self-healing-function
            else NormalStrategy -- build the heal
                          ((buildHeal fieldIndex fieldBuildOn fieldCountOn g)
                        ++ (getValueOfSlotI fieldBuildOn 1))
            where fieldIndex = getFreeFieldWithLife' 0 4 9999 (prop g) -- find a Field with 10000 life or more
                  fieldBuildOn = getFieldBuildOn g -- find the best field to build this function in
                  fieldCountOn = 0 

-- | Prepares a Healing-function. It has to be applied with n. 
buildHeal::Int->Int->Int->Game->[Action]
buildHeal index buildOn countOn g = 
                           (initHeal (prop g))
                        ++ (countToNumber index countOn (prop g))
                        ++ (getValueOfSlotI buildOn countOn)
                        ++ [(1,Help,buildOn), -- Help i
                            (2,Zero,buildOn)] -- Help i 0
                        

-- | writes 9999 in field 1
initHeal::Environment->[Action]
initHeal e = countToNumber healN 1 e

-- | writes 11112 in field 2
initAttack::Environment->[Action]
initAttack e = countToNumber attackN 2 e

-- | heals field 0 without reducing the life of another field
multyplyLife::Game->Strategy 
multyplyLife g = (NormalStrategy 
                        ((nextBigNumber heal 0 g)
                     ++ [(2,Help,fieldBuildOn),
                         (2,Zero,fieldBuildOn),
                         (2,Zero,fieldBuildOn)]
                     ++ (getValueOfSlotI fieldBuildOn 0)))
                 where fieldBuildOn = getFieldBuildOn g
                       slot0 = getSlotI 0 (prop g)
                       heal = getLife (fst slot0) -1


-- | counts fast to a big number near n, this is used if the number should be smaller then n and big            
nextBigNumber::Int->Int->Game->[Action]
nextBigNumber n buildOn g = if (n < number1) 
                              then countToNumber n buildOn (prop g) --small n? use normal count function
                              else if ((4 * numberBuildOn) > number1) && (numberBuildOn < n) && (numberBuildOn > 0)--allredy a big number? then use the existing number
                                then doubleAsLongAsPosible numberBuildOn
                                else if number1 > 1 --number1 can be used
                                       then (getValueOfSlotI buildOn 1)
                                         ++ (doubleAsLongAsPosible number1)
                                       else (countToNumber 1 buildOn (prop g)) --start with 1 and double
                                                ++ doubleAsLongAsPosible 1
                            where numberBuildOn = isNumber (getSlotI buildOn (prop g))
                                  number1 = isNumber (getSlotI 1 (prop g))
                                  doubleAsLongAsPosible::Int->[Action]
                                  doubleAsLongAsPosible i = if (2*i) > n 
                                                                then []
                                                                else [(1,Dbl,buildOn)] ++ doubleAsLongAsPosible (2*i)
                                  


-- | Revives field with a given index (field 0 is revived and healed at the same time, all other fields are just revived)
newRevive::Strategy -> Int -> Game -> Strategy 
--revive and heal in one turn (field 0 will come back with 10999 life, field0 is important and many strategies base on killing field 0 again and again.)
newRevive s 0 g =InteruptingStrategy ((initHeal (prop g))
                                    ++ (countToNumber usedToHeal fieldCountOn (prop g))
                                    ++ [(1,Help,fieldCountOn), -- Help i
                                        (2,Zero,fieldCountOn), -- Helo i 0
                                        (1,K,fieldCountOn), 
                                        (1,S,fieldCountOn),
                                        (2,Get,fieldCountOn), -- S(K(Help i 0)) (get)
                                        (1,K,fieldCountOn),
                                        (1,S,fieldCountOn),
                                        (2,Succ,fieldCountOn), -- S(K(help i j)) (get (succ)) 
                                        (2, Revive, fieldBuildOn),
                                        (1, S  , fieldBuildOn)] 
                                    ++ (getValueOfSlotI fieldBuildOn fieldCountOn) -- S (rev) [help i 0 (get (succ))]
                                    ++ [(2, Zero,fieldBuildOn)], NormalStrategy [])
                where fieldCountOn = getFieldBuildOn g
                      fieldBuildOn = getFirstI' (prop g) (1+fieldCountOn) -- getFreeFieldWithLife' 0 fieldBuildOn 9999 (prop g)
                      usedToHeal = getFreeFieldWithLife' 0 5 9999 (prop g) -- find a Field with 10000 life or more
newRevive s i g = (InteruptingStrategy (((countToNumber' i free) ++ [(1, Revive, free)]), NormalStrategy [])) --TODO test if strategy can be continued and return it
                where free = getFieldBuildOn g

-- | Kill enemy slot0 and init our Field 254 (Field 254 can kill the enemy slot0 immediatly after it was revived) 
firstMoves::Strategy
firstMoves = (NormalStrategy ((countToNumber' 254 0) ++
                         [(1,K,0),
                          (2,K,254),
                          (2,Dec,254),
                          (1,S,254),
                          (2,Succ,254),
                          (1,S,254),
                          (1,S,254)]
                      ++ (getValueOfSlotI 254 0) --S (S (S (K Dec) Succ)) (K 254) in Field 254
                                        -- apply get to this and it will reduce life of enemy slot0 by 1 in only 1 turn
                      ++ [(2,Put,0),
                          (1,Succ,0),  --Field 0 -> 255
                          (2,Zero,3),-- Field 3 = zero
                          (1,Succ,3),-- Field 3 = 1
                          (1,Attack,3),-- Field 3 = attack 1
                          (1,K,3),
                          (1,S,3),
                          (2,Get,3),
                          (2,Zero,3), --attack 1 (255) in Field 3
                          (1,K,3),
                          (1,S,3),
                          (2,Get,3), --S(K(attack (1) (255) ))(Get) in Field 3
                          (2,Zero,2), -- Field 2 = zero
                          (1,Attack,2),-- Field 2 = attack zero
                          (1,K,2),-- Field 2 = K (attack zero)
                          (1,S,2),-- Field 2 = S (K (attack zero))
                          (2,Get,2),-- Field 2 = S (K (attack zero)) (get)
                          (2,Zero,2),--attack (zero) (255) in Field 2
                          
                          (1,Dbl,0),
                          (1,Dbl,0),
                          (1,Dbl,0),
                          (1,Dbl,0),
                          (1,Dbl,0),-- 8160 in Field 0
                          
                          (2,Zero,3), -- First attack on Field 0
                          
                          (1,K,2),
                          (1,S,2),
                          (2,Get,2), --S(K(attack (zero) (255) ))(Get)in Field 2
                          (2,Zero,2)])) --Release the Firestorm! (kill slot0)



